from sc_engine.repositories import Repository

class WaitingDataRepository(object):
    """Store messages that should be run when a later message is received.

    Sometimes the contents of a message cannot be handled because other data is
    necessary. The WaitingDataRepository allows you to record messages that need
    to be run at a later time when a specific message finally comes in.
    """
    BINDING_ERROR = 'Message %s does not have attribute %s to bind to.'

    def __init__(self, store):
        self.store = store

    def add(self, message, message_type, bindings):
        """Add a message that waits to be run.

        ``message``: The message waiting to be run.
        ``message_type``: The type of message to wait for.
        ``bindings``: A dictionary of attributes that must match the incoming
            message in order for the message to be sent out.
        """
        if message_type not in self.store:
            self.store[message_type] = []

        self.store[message_type].append((bindings, message))

    def _passes_bindings(self, bindings, message):
        for name, value in bindings.items():
            if not hasattr(message, name):
                raise ValueError(self.BINDING_ERROR % (message, name))

            if getattr(message, name) != value:
                return False

        return True

    def run(self, message):
        """Returns messages that have been waiting.

        ``message``: The message that was received that another message might be
        waiting on to run.

        Will check all waiting messages that are waiting for this type of
        message. Any whose bindings match this message (for each name in the
        binding, the attribute of the message has the same value) are returned,
        and are no longer waiting.
        """
        message_type = type(message)
        if message_type not in self.store:
            return []

        outgoing = set()
        failed_bindings = []
        message_checks = self.store[message_type]

        for bindings, outgoing_message in message_checks:
            if self._passes_bindings(bindings, message):
                outgoing.add(outgoing_message)
            else:
                failed_bindings.append((bindings,outgoing_message))

        if len(outgoing) == 0:
            return []

        if len(failed_bindings) == 0:
            del self.store[message_type]
        else:
            self.store[message_type] = failed_bindings

        return list(outgoing)







